package com.googlecode.dgwt.client.dojo.core;

import com.google.gwt.core.client.JavaScriptObject;
import com.googlecode.dgwt.client.dojo.util.JsUtil;


/**
 * Represents an native JavaScript function.
 * 
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class JsFunction extends JsObject {

    protected JsFunction() {
    }

    /**
     * Creates an new JavaScript function from parameters. <br />
     * The syntax looks like instantiating an native function: <br />
     * <br />
     * <code> JsFunction.create("param1", "param2", "paramN", "return param1 + param2 + paramN;"); </code>
     * <br />
     * <br />
     * Note that the last parameter is the function body.
     * 
     * @param args
     *            parameter names and function body
     * @return an {@link JsFunction} object
     */
    public static JsFunction create(String ...args){
        switch (args.length) {
            case 0: {
                return createFunction().cast();
            }
            case 1: {
                return createAux(args[0]);
            }
            default: {
                String fn = "";
                
                for (int i=0, len=args.length; i<len; i++){
                    
                    if (i != 0) {
                        fn += ",";
                    }
                    
                    fn += "\"" + JsUtil.escapeString(args[i]) + "\"";
                }
                return createAux("return new Function(" + fn + ");").call();
            }
        }
    }
    
    private static native JsFunction createAux(String fn) /*-{
        return new Function(fn);
    }-*/;

    /**
     * Invoke the native {@link JsFunction} statements
     * 
     * @param <T>
     *            return type
     * @param args
     *            arguments to be passed as parameters
     * @return the function result, or <code>null</code> if the result is
     *         <code>undefined</code>
     */
    public final <T> T call(){
        return call(null);
    }

    /**
     * Invoke the native {@link JsFunction} statements
     * 
     * @param <T>
     *            return type
     * @param self
     *            object to be used as <code>this</code> inside the function
     *            statements
     * @param args
     *            arguments to be passed as parameters
     * @return the function result, or <code>null</code> if the result is
     *         <code>undefined</code>
     */
    public final <T> T call(Object self, Object ...args){
        return apply(self, args);
    }

    /**
     * Invoke the native {@link JsFunction} statements
     * 
     * @param <T>
     *            return type
     * @param self
     *            object to be used as <code>this</code> inside the function
     *            statements
     * @param args
     *            arguments to be passed as parameters
     * @return the function result, or <code>null</code> if the result is
     *         <code>undefined</code>
     */
    public final <T> T apply(Object self, Object[] args) {
        return apply(self, JsArray.as(args)); 
    }
        
    /**
     * Invoke the native {@link JsFunction} statements
     * 
     * @param <T>
     *            return type
     * @param self
     *            object to be used as <code>this</code> inside the function
     *            statements
     * @param args
     *            arguments to be passed as parameters
     * @return the function result, or <code>null</code> if the result is
     *         <code>undefined</code>
     */
    public final native <T> T apply(Object self, JsArray<?> args) /*-{
        return this.apply(self || $wnd, args);
    }-*/; 

    /**
     * Invoke the native {@link JsFunction} statements
     * 
     * @param <T>
     *            return type
     * @param self
     *            object to be used as <code>this</code> inside the function
     *            statements
     * @param args
     *            arguments to be passed as parameters
     * @return the function result, or <code>null</code> if the result is
     *         <code>undefined</code>
     */
    public final <T extends JavaScriptObject> T callNative(Object self, Object ...args){
        return applyNative(self, args);
    }

    /**
     * Invoke the native {@link JsFunction} statements
     * 
     * @param <T>
     *            return type
     * @param self
     *            object to be used as <code>this</code> inside the function
     *            statements
     * @param args
     *            arguments to be passed as parameters
     * @return the function result, or <code>null</code> if the result is
     *         <code>undefined</code>
     */
    public final <T extends JavaScriptObject> T applyNative(Object self, Object[] args) {
        return applyNative(self, JsArray.as(args));
    }

    /**
     * Invoke the native {@link JsFunction} statements
     * 
     * @param <T>
     *            return type
     * @param self
     *            object to be used as <code>this</code> inside the function
     *            statements
     * @param args
     *            arguments to be passed as parameters
     * @return the function result, or <code>null</code> if the result is
     *         <code>undefined</code>
     */
    public final native <T extends JavaScriptObject> T applyNative(Object self, JsArray<?> args) /*-{
        return this.apply(self || $wnd, args);
    }-*/; 

}
